//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Graphics/GLshell/VertexAttribs.h"

namespace Engine
{
namespace Primitives
{
	/*

	//
	// Poly Line
	//

	template <typename T, uint8 I>
	struct PolyLine
	{
	// types
	public:
		typedef PolyLine< T, I >	Self;
		typedef TVec< T, I >		vec_t;
		typedef TLine< T, I >		line_t;
		typedef color4u				color_t;
		typedef T					value_t;

		struct Reader;

		struct EPrimitive
		{
			enum type
			{
				NONE = 0,
				ARC,
				LINE,
				SPLINE,
			};
		};

		struct BaseSegment
		{
			vec_t		point;
			color_t		color;
			///
			BaseSegment () {}
		};

		struct Line : BaseSegment
		{
			vec_t		normal;
			///
			Line () {}
		};

		struct Arc : BaseSegment
		{
			value_t		radius,
						height;		// distance from chord center to circle center
			///
			Arc () : radius(0), height(0) {}
		};

		struct Spline : BaseSegment
		{
			///
			Spline () {}
		};

	private:
		typedef TUnion< BaseSegment, Line, Arc, Spline >	_Segment_t;
		typedef array< _Segment_t >							_SegmentsArray_t;


	// variables
	private:
		_SegmentsArray_t	_data;
		BaseSegment			_startSegment;


	// methods
	public:
		PolyLine ();

		Self &	SetStartPoint (const vec_t &point, color_t color = color_t());
		Self &	AddLine (const vec_t &endPoint, color_t color = color_t());
		Self &	AddSpline (const vec_t &endPoint, color_t color = color_t());
		Self &	AddArcWithRadius (const vec_t &endPoint, value_t radius, color_t color = color_t());
		Self &	AddArcWithHeight (const vec_t &endPoint, value_t height, color_t color = color_t());
		Self &	AddSmoothLine (const vec_t &endPoint, T smooth = T(0.5), color_t color = color_t());	// transform 2 lines to line + arc + line

		Reader	GetReader ()	const	{ return Reader( *this ); }
		usize	Count ()		const	{ return _data.Count(); }

	private:
		vec_t const &	_GetPoint (uint offset) const;
	};


	typedef PolyLine< float, 2 >	PolyLine2D;
	typedef PolyLine< float, 3 >	PolyLine3D;

	

	//
	// Poly Line Reader
	//

	template <typename T, uint8 I>
	struct PolyLine<T,I>::Reader
	{
	// types
	public:
		typedef PolyLine< T, I >					poly_line_t;
		typedef Reader								Self;
		typedef typename poly_line_t::vec_t			vec_t;
		typedef typename poly_line_t::value_t		value_t;
		typedef typename poly_line_t::line_t		line_t;
		typedef typename poly_line_t::color_t		color_t;
		typedef typename poly_line_t::EPrimitive	EPrimitive;

		typedef typename poly_line_t::Line			Line;
		typedef typename poly_line_t::Arc			Arc;
		typedef typename poly_line_t::Spline		Spline;
		typedef typename poly_line_t::BaseSegment	BaseSegment;

	private:
		typedef typename poly_line_t::_Segment_t		_Segment_t;
		typedef typename poly_line_t::_SegmentsArray_t	_SegmentsArray_t;


	// variables
	private:
		poly_line_t		_polyLine;
		BaseSegment		_startSegment;
		isize			_pos;


	// methods
	public:
		Reader (const Self &reader);
		Reader (const poly_line_t &polyLine);

		bool NextChunk ();
		bool PrevChunk ();
		void MoveToStart ();
		void MoveToEnd ();

		Line const &		GetLine ()			const;
		Spline const &		GetSpline ()		const;
		Arc const &			GetArc ()			const;
		
		void GetLine (OUT line_t &line, OUT vec_t &norm, OUT color_t &color) const;
		void GetLine (OUT vec_t &endPoint, OUT vec_t &norm, OUT color_t &color) const;

		template <typename TSpline>
		void GetSpline (OUT TSpline &spline, OUT color_t &color) const;
		
		void GetArc (OUT vec_t &center, OUT line_t &chord, OUT value_t &radius, OUT color_t &color) const;
		void GetArc (OUT vec_t &center, OUT value_t &radius, OUT vec_t &startEndAngle, OUT color_t &color) const;

		BaseSegment const &	GetStartSegment ()	const	{ return _startSegment; }
		vec_t const &		GetStartPoint ()	const	{ return _startSegment.point; }
		color_t const &		GetStartColor ()	const	{ return _startSegment.color; }

		typename poly_line_t::EPrimitive::type	GetType ()	const;
	};



	
//--------------------------- PolyLine ----------------------------//
/*
=================================================
	constructor
=================================================
*
	template <typename T, uint8 I>
	inline PolyLine<T,I>::PolyLine ()
	{}

/*
=================================================
	SetStartPoint
=================================================
*
	template <typename T, uint8 I>
	inline PolyLine<T,I> &  PolyLine<T,I>::SetStartPoint (const vec_t &point, color_t color)
	{
		_data.ClearMem();
		_startSegment.point = point;
		_startSegment.color	= color;
		return *this;
	}
	
/*
=================================================
	AddLine
=================================================
*
	template <typename T, uint8 I>
	inline PolyLine<T,I> &  PolyLine<T,I>::AddLine (const vec_t &endPoint, color_t color)
	{
		Line	line;
		line.point	= endPoint;
		line.normal	= line_t( _GetPoint(0), endPoint ).Normal();
		line.color	= color;

		_data.PushBack( line );
		return *this;
	}
	
/*
=================================================
	AddSpline
=================================================
*
	template <typename T, uint8 I>
	inline PolyLine<T,I> &  PolyLine<T,I>::AddSpline (const vec_t &endPoint, color_t color)
	{
		Spline	sp;
		sp.point	= endPoint;
		sp.color	= color;

		_data.PushBack( sp );
		return *this;
	}

/*
=================================================
	AddSmoothLine
=================================================
*
	template <typename T, uint8 I>
	inline PolyLine<T,I> &  PolyLine<T,I>::AddSmoothLine (const vec_t &endPoint, T smooth, color_t color)
	{
		if ( _data.Empty() )
			return AddLine( endPoint, color );

		line_t			prev	= line_t( _GetPoint(1), _GetPoint(0) );
		line_t			next	= line_t( prev.End(), endPoint );
		const vec_t		p_norm	= prev.Normal();
		const vec_t		n_norm	= next.Normal();

		T		prev_len,
				next_len;
		vec_t	prev_n,
				next_n;

		prev.Vector().GetNormLength( prev_n, prev_len );
		next.Vector().GetNormLength( next_n, next_len );

		if ( All( prev_n == next_n ) )
			return AddLine( endPoint, color );

		const T	len	 = Min( prev_len, next_len ) * Clamp( smooth, T(0.0), T(1.0) ) * T(0.5);

		prev.End()   = prev.End() - prev_n * len;
		prev.Begin() = prev.End() + p_norm;

		next.Begin() = next.Begin() + next_n * len;
		next.End()   = next.Begin() + n_norm;

		_data.Back().Cast< Line >().point = prev.End();

		vec_t	c;
		prev.GetRayIntersection( next, c );

		const T	r = CopySign( c.y - line_t( prev.End(), next.Begin() ).Center().y, prev.End().Distance( c ) );

		AddArcWithRadius( next.Begin(), r, color );
		AddLine( endPoint, color );
		
		return *this;
	}
	
/*
=================================================
	AddArcWithHeight
=================================================
*
	template <typename T, uint8 I>
	inline PolyLine<T,I> &  PolyLine<T,I>::AddArcWithHeight (const vec_t &endPoint, value_t height, color_t color)
	{
		Arc	arc;
		arc.point	= endPoint;
		arc.color	= color;
		arc.height	= height;
		arc.radius	= Pythagoras::Diagonal( line_t( _GetPoint(0), endPoint ).Length() * T(0.5), height );

		_data.PushBack( arc );
		return *this;
	}
	
/*
=================================================
	AddArcWithRadius
=================================================
*
	template <typename T, uint8 I>
	inline PolyLine<T,I> &  PolyLine<T,I>::AddArcWithRadius (const vec_t &endPoint, value_t radius, color_t color)
	{
		Arc	arc;
		arc.point	= endPoint;
		arc.color	= color;
		arc.radius	= Abs( radius );
		arc.height	= CopySign( radius, Pythagoras::Side( line_t( _GetPoint(0), endPoint ).Length() * T(0.5), arc.radius ) );

		_data.PushBack( arc );
		return *this;
	}

/*
=================================================
	_GetPoint
=================================================
*
	template <typename T, uint8 I>
	inline TVec<T,I> const &  PolyLine<T,I>::_GetPoint (uint offset) const
	{
		return ( offset >= _data.Count() ? _startSegment.point :
				 _data[ _data.LastIndex() - offset ].Cast< BaseSegment >().point );
	}
	



//---------------------------- Reader -----------------------------//
/*
=================================================
	constructor
=================================================
*
	template <typename T, uint8 I>
	inline PolyLine<T,I>::Reader::Reader (const Reader &reader) :
		_polyLine(reader._polyLine), _startSegment(reader._startSegment),
		_pos(reader._pos)
	{
	}

/*
=================================================
	constructor
=================================================
*
	template <typename T, uint8 I>
	inline PolyLine<T,I>::Reader::Reader (const poly_line_t &polyLine) :
		_polyLine(polyLine), _startSegment(polyLine._startSegment),
		_pos(0)
	{
	}

/*
=================================================
	NextChunk
=================================================
*
	template <typename T, uint8 I>
	inline bool PolyLine<T,I>::Reader::NextChunk ()
	{
		if ( _pos >= (isize)_polyLine._data.LastIndex() )
		{
			MoveToEnd();
			return false;
		}

		BaseSegment const &	seg = _polyLine._data[ _pos ].Cast< BaseSegment >();

		_startSegment = seg;
		
		++_pos;
		return true;
	}
	
/*
=================================================
	PrevChunk
=================================================
*
	template <typename T, uint8 I>
	inline bool PolyLine<T,I>::Reader::PrevChunk ()
	{
		const bool res = _pos <= 0;

		if ( (--_pos) <= 0 )
		{
			MoveToStart();
			return res;
		}
		else
		{
			BaseSegment const &	seg = _polyLine._data[ _pos-1 ].Cast< BaseSegment >();
			_startSegment = seg;
			return true;
		}
	}

/*
=================================================
	MoveToStart
=================================================
*
	template <typename T, uint8 I>
	inline void PolyLine<T,I>::Reader::MoveToStart ()
	{
		_pos			= 0;
		_startSegment	= _polyLine._startSegment;
	}

/*
=================================================
	MoveToEnd
=================================================
*
	template <typename T, uint8 I>
	inline void PolyLine<T,I>::Reader::MoveToEnd ()
	{
		_pos = _polyLine._data.LastIndex();
		
		if ( _pos <= 0 )
		{
			_startSegment	= _polyLine._startSegment;
		}
		else
		{
			BaseSegment const &	seg = _polyLine._data[ _pos-1 ].Cast< BaseSegment >();
			_startSegment	= seg;
		}
	}
	
/*
=================================================
	GetLine
=================================================
*
	template <typename T, uint8 I>
	inline typename PolyLine<T,I>::Reader::Line const &  PolyLine<T,I>::Reader::GetLine () const
	{
		CHECK( GetType() == EPrimitive::LINE );
		return _polyLine._data[ _pos ].Cast< Line >();
	}
	
/*
=================================================
	GetSpline
=================================================
*
	template <typename T, uint8 I>
	inline typename PolyLine<T,I>::Reader::Spline const &  PolyLine<T,I>::Reader::GetSpline () const
	{
		CHECK( GetType() == EPrimitive::SPLINE );
		return _polyLine._data[ _pos ].Cast< Spline >();
	}
	
/*
=================================================
	GetArc
=================================================
*
	template <typename T, uint8 I>
	inline typename PolyLine<T,I>::Reader::Arc const & PolyLine<T,I>::Reader::GetArc () const
	{
		CHECK( GetType() == EPrimitive::ARC );
		return _polyLine._data[ _pos ].Cast< Arc >();
	}

/*
=================================================
	GetLine
=================================================
*
	template <typename T, uint8 I>
	inline void PolyLine<T,I>::Reader::GetLine (OUT vec_t &endPoint, OUT vec_t &norm, OUT color_t &color) const
	{
		CHECK( GetType() == EPrimitive::LINE );

		Line const &	data = _polyLine._data[ _pos ].Cast< Line >();

		endPoint = data.point;
		norm	 = data.normal;
		color	 = data.color;
	}

/*
=================================================
	GetLine
=================================================
*
	template <typename T, uint8 I>
	inline void PolyLine<T,I>::Reader::GetLine (OUT line_t &line, OUT vec_t &norm, OUT color_t &color) const
	{
		CHECK( GetType() == EPrimitive::LINE );

		Line const &	data = _polyLine._data[ _pos ].Cast< Line >();

		line	= line_t( _startSegment.point, data.point );
		norm	= data.normal;
		color	= data.color;
	}

/*
=================================================
	GetSpline
=================================================
*
	template <typename T, uint8 I>
	template <typename TSpline>
	inline void PolyLine<T,I>::Reader::GetSpline (OUT TSpline &spline, OUT color_t &color) const
	{
		CHECK( GetType() == EPrimitive::SPLINE );

		_SegmentsArray_t const &	data = _polyLine._data;

		_Segment_t const &	d0 = data[ Max( _pos-2, 0 ) ];
		_Segment_t const &	d1 = data[ Max( _pos-1, 0 ) ];
		_Segment_t const &	d2 = data[ _pos ];
		_Segment_t const &	d3 = data[ Min( _pos+1, (isize)data.LastIndex() ) ];
		/*
		_Segment_t const &	d0 = data[ Max( _pos-1, 0 ) ];
		_Segment_t const &	d1 = data[ _pos ];
		_Segment_t const &	d2 = data[ Min( _pos+1, (isize)data.LastIndex() ) ];
		_Segment_t const &	d3 = data[ Min( _pos+2, (isize)data.LastIndex() ) ];
		* /
		spline = TSpline(
			( d0.GetCurrentTypeId() == TypeId< Arc >() ? d1.Cast< BaseSegment >().point : d0.Cast< BaseSegment >().point ),
			d1.Cast< BaseSegment >().point,
			d2.Cast< BaseSegment >().point,
			( d3.GetCurrentTypeId() == TypeId< Arc >() ? d2.Cast< BaseSegment >().point : d3.Cast< BaseSegment >().point )
		);

		color = d1.Cast< BaseSegment >().color;
	}

/*
=================================================
	GetArc
=================================================
*
	template <typename T, uint8 I>
	inline void PolyLine<T,I>::Reader::GetArc (OUT vec_t &center, OUT line_t &chord, OUT value_t &radius, OUT color_t &color) const
	{
		CHECK( GetType() == EPrimitive::ARC );

		Arc const &	data = _polyLine._data[ _pos ].Cast< Arc >();

		chord	= line_t( _startSegment.point, data.point );
		radius	= data.radius;
		center	= chord.Center();
		color	= data.color;
		center += chord.Normal() * data.height;
	}

/*
=================================================
	GetArc
=================================================
*
	template <typename T, uint8 I>
	inline void PolyLine<T,I>::Reader::GetArc (OUT vec_t &center, OUT value_t &radius, OUT vec_t &startEndAngle, OUT color_t &color) const
	{
		STATIC_ASSERT( I == 2, "only 2D supported" );

		typedef TCircle< T >	circle_t;

		line_t	chord;
		GetArc( center, chord, radius, color );

		circle_t	c( center, radius );

		startEndAngle = vec_t( c.GetAngle( chord.Begin() ), c.GetAngle( chord.End() ) );
	}
	
/*
=================================================
	GetType
=================================================
*
	template <typename T, uint8 I>
	inline typename PolyLine<T,I>::EPrimitive::type	 PolyLine<T,I>::Reader::GetType () const
	{
		if ( _polyLine._data.Empty() )
			return EPrimitive::NONE;

		const CompileTime::TypeId_t  id = _polyLine._data[ _pos ].GetCurrentTypeId();

		if ( id == TypeId< Line >() )
			return EPrimitive::LINE;
		else
		if ( id == TypeId< Arc >() )
			return EPrimitive::ARC;
		else
		if ( id == TypeId< Spline >() )
			return EPrimitive::SPLINE;
		else
			return EPrimitive::NONE;
	}
*/

}	// Primitives
}	// Engine
